/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package biofilter.records;

import biofilter.util.MappedPosition;
import java.util.StringTokenizer;
import biofilter.util.SequencePosition;
import java.util.*;
import java.io.*;

/**
 *
 * @author Daniel Quest
 * @date   Feb23 2010
 * ~refactored up from the KB project.
 * This file may need some work to get this up and running.
 */
public class GFFRecord implements Record, MappedPosition, Comparable, java.io.Serializable {
    private SequencePosition sp;
    private String seqid;
    private String source;
    private String type;
    private int start;
    private int end;
    public double score;
    private char strand;
    private String phase;
    private String attributes;
    public GFFRecord(){

    }

    private String record = null;
    public String getSeqid(){ return seqid;}
    public String getSource(){return source;}
    public String getType(){ return type;}
    public int getStart(){ return start;}
    public int getEnd(){ return end;}
    public double getScore(){ return score;}
    public char getStrand(){ return strand;}
    public String getPhase(){return phase;}
    public String getAttributes(){ return attributes;}

    public void setAttributes(String attributes) {
        this.attributes = attributes;
    }

    public void setEnd(int end) {
        this.end = end;
    }

    public void setPhase(String phase) {
        this.phase = phase;
    }

    public void setScore(double score) {
        this.score = score;
    }

    public void setSeqid(String seqid) {
        this.seqid = seqid;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public void setStrand(char strand) {
        this.strand = strand;
    }

    public void setType(String type) {
        this.type = type;
    }



    

    public char parseStrand(int i){
        if ( i == -1) return '-';
        else return '+';
    }
    public char parseStrand(String s){
        if (s.contains("+")) return '+';
        else if(s.contains("-")) return '-';
        else if(s.contains("1")) return '+';
        else return '.';
    }

    private String[] splitOnSpaces(String line){
        int counter = 0;
        String cols[] = new String[10];
        cols = line.split(" ");
        if (cols.length == 9){ return cols;
        }else{
            cols = new String[10];
            //fine, lets do it the hard way!
            StringTokenizer st = new StringTokenizer(line);
            while (st.hasMoreTokens()){
                if(counter < 9){
                    cols[counter] = st.nextToken();
                }
                if(counter >= 9){
                    cols[8] = cols[8] + " ";
                    cols[8] = cols[8] + st.nextToken();
                }
                counter++;
            }
            return cols;

        }
    }

    public boolean isInteger( String input )  {
        try {
            Integer.parseInt( input );
            return true;
        }catch( Exception e){
                return false;
        }
    }

    public boolean isDouble( String input )  {
        try {
            Double.parseDouble( input );
            return true;
        }catch( Exception e){
            return false;
        }
    }

    /*
     * Some colums are suposed to hold a double value (e.g. score) but sometimes, they hold '.' or other strings
     * if this is the case, we need to handle this, in this software package, we will force 0.0 on such data.
     */ 
    private double handleDoubleCol(String s){
        if(s.equalsIgnoreCase("."))
            return 0.0;
        if(isDouble(s)){
            return Double.parseDouble( s );
        }
        return 0.0;
    }

    private int handleIntCol(String s){
        if(s.equalsIgnoreCase("."))
            return 1;
        if(isInteger(s)){
            return Integer.parseInt( s );
        }
        return 1;
    }

    public boolean parseGFFLine(String line){
        String cols[] = line.split("\t");
        if (cols.length == 1){
            cols = splitOnSpaces(line);
        }      
        Integer ll = new Integer("1");
        //System.err.println(cols.length);
        if ( cols.length == 9 || cols.length == 10){
            seqid = cols[0];
            source = cols[1];
            type = cols[2];
            start = handleIntCol(cols[3]);
            end = handleIntCol(cols[4]);
            score = handleDoubleCol(cols[5]);
            strand = parseStrand( cols[6] );
            phase = cols[7];
            attributes = cols[8];
            return true;
        }else if(cols.length == 8 && cols[1].endsWith("NGS")){  //note this may need to become more complex
            return parseNGSGFF(line);
        }else{
            return false;
        }
        //TODO:
    }

    /**
     * parseNGSGFF is a function for parsing gff data associated with tiling arrays as below...
     * TODO: Build error handling...
     * @param line -
     * CP000568        NGS     OID21551        3843219 3843277 01      .       .
     * @return
     */
    public boolean parseNGSGFF(String line){
            String cols[] = line.split("\t");
            Double dd = new Double("0");
            Integer ll = new Integer("1");
            seqid = cols[0];
            source = cols[1];
            type = cols[2];
            start = ll.parseInt(cols[3]);
            end = ll.parseInt(cols[4]);
            score = dd.parseDouble(cols[5]);
            strand = parseStrand( cols[6] );
            //not sure if the following are right as they abondoned the standard.
            phase = cols[7];
            return true;
    }

    public String formatGFF(){
        if(seqid == null ){  //more robust check needed here!
            return null;
        }
        String s = "";
        s+= seqid;
        s+= "\t" + source;
        s+= "\t" + type;
        s+= "\t" + start;
        s+= "\t" + end;
        s+= "\t" + this.getScore();
        s+= "\t" + strand;
        s+= "\t" + phase;
        s+= "\t" + attributes;
        //s+= "\n";  //this is to be consistent with the rest of the architecture
        return s;
    }

    public SequencePosition castGFFtoPosition(){
        SequencePosition sp = new SequencePosition();
        sp.set(start, end, strand);
        return sp;
    }

    /**
     * Takes a gff file of probes and places in the last collumn the gene that overlaps this probe.
     * @param gffProbeFilePath
     * @param gffAnnoFilePath
     * @param outFilePath 
     */
    public void steveBrownOct31_2009MaryProbesToAnno(String gffProbeFilePath, String gffAnnoFilePath, String outFilePath){
        HashMap hm = new HashMap();  //hash map to hold the probes.
        try{
            //first things first... load the positions of all known genes into memory...
            BufferedReader in = new BufferedReader(new FileReader(gffAnnoFilePath));
            String line;
            while ((line = in.readLine()) != null) {
                GFFRecord gff = new GFFRecord();
                gff.parseGFFLine(line);
                //The original GFF line is not needed for further processing, just the position of the gene.
                SequencePosition sp = gff.castGFFtoPosition();
                hm.put(sp, gff.attributes);
                //System.err.println(hm.toString());
            }
            in.close();
            in = new BufferedReader(new FileReader(gffProbeFilePath));
            BufferedWriter out = new BufferedWriter(new FileWriter(outFilePath));
            while ((line = in.readLine()) != null) {
                boolean printed = false;
                GFFRecord gff = new GFFRecord();
                gff.parseGFFLine(line);
                SequencePosition spProbe = gff.castGFFtoPosition();
                Set s = hm.entrySet();
                Iterator i = s.iterator();
                while(i.hasNext()){
                    Map.Entry entry = (Map.Entry) i.next();
                    SequencePosition spGene = (SequencePosition) entry.getKey();
                    String geneDisc = (String) entry.getValue();
                    if(spGene.overlaps(spProbe)){
                        gff.attributes = geneDisc;
                        out.write(gff.formatGFF());
                        printed = true;
                        break;
                    }
                }
            }
            in.close();
            out.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    public String get() {
        this.record = this.formatGFF();
        return this.record;
    }

    public void set(String r) throws IllegalArgumentException {
        this.record = r;
        parseGFFLine(r);
    }

    public int length() {
        return end - start + 1;
    }

    public double getWeight() {
        return this.score;
    }

    public String getQueryID() {
        return "."; //undefined... gff is for features of a sequence only 1 sequence exists!
    }

    public String getSubjectID() {
        return this.source;
    }

    @Override
    public int compareTo(Object o) {
        GFFRecord gff = (GFFRecord) o;
        if (this.record.equalsIgnoreCase( gff.get()) )
            return 0;
        else if( this.start > gff.start )
            return 1;
        else if( this.end > gff.end )
            return 1;
        else if(this.start == gff.start && this.end == gff.end)
            return 0;
        //else if(this.strand > gff.strand)
        //    return 1;
        else
            return -1;
    }




}
